"""
@Author    : ghenyar
@Time      : 2025/9/4 16:51
@File      : wx
@Desc      : 微信小程序服务端 API 封装。
"""
import json
from typing import Any, Dict, Union

import requests


class WX:

    def __init__(self, app_id: str, secret: str):
        """
        小程序初始化
        :param app_id: 小程序app_id
        :param secret: 小程序秘钥(secret)
        """
        self.base_url = "https://api.weixin.qq.com"
        self.app_id = app_id
        self.secret = secret
        # 使用 Session 复用TCP连接，提升性能
        self._session = requests.Session()

    def _get_access_token(self) -> str | None:
        """
        获取接口调用凭据(access_token)
        :return: access_token或None
        """
        url = f"{self.base_url}/cgi-bin/token?grant_type=client_credentials&appid={self.app_id}&secret={self.secret}"
        result = self._make_request("get", url)
        if "access_token" not in result:
            print(f"获取 access_token 失败: {result.get('errmsg', '未知错误')}")
            return None
        return result["access_token"]

    def code_login(self, code: str) -> Dict[str, Any] | None:
        """
        根据小程序提供的js_code登录
        :param code: js_code
        :return: 成功返回的数据或错误提示
        """
        url = f"{self.base_url}/sns/jscode2session?appid={self.app_id}&secret={self.secret}&js_code={code}&grant_type=authorization_code"
        result = self._make_request("get", url)
        # 如果 API 返回了错误码，则进行转换和包装
        if 'errcode' in result and result['errcode'] != 0:
            error_messages = {
                -1: "系统繁忙，请稍后重试",
                40029: "code 无效",
                45011: "API 调用过于频繁，请稍候再试",
                40226: "高风险用户，小程序登录被拦截",
                40163: "code 已被使用过或已过期"
            }
            message = error_messages.get(result['errcode'], result.get('errmsg', '未知错误'))
            return {"errcode": result['errcode'], "message": message}
        return {
            "errcode":0,
            "openid": result['openid'],
            "session_key": result['session_key'],
            "unionid":result['unionid'] if 'unionid' in result else None,
        }

    def getwxacodeunlimit(self,page:str, scene:str, width:int=320,
                                auto_color:bool=False,line_color:dict=None,is_hyaline:bool=True)->None| Union[bytes]:
        """
        获取小程序码
        :param page: 页面 page，例如 pages/index/index
        :param scene: 参数
        :param width: 大小
        :param auto_color: 自动配置线条颜色
        :param line_color: 线条颜色
        :param is_hyaline: 是否需要透明底色
        :return:
        """
        if line_color is None:
            line_color = {"r": 0, "g": 147, "b": 233}
        url = f"{self.base_url}/wxa/getwxacodeunlimit?access_token={self._get_access_token()}"
        data = {
            "page": page,
            "scene": scene,
            "width": width,
            "auto_color": auto_color,
            "line_color": line_color,
            "is_hyaline": is_hyaline,
        }
        result = self._make_request("post", url,raw=True, json=data)
        if 'errcode' in result:
            return None
        return result


    def _make_request(self, method: str, url: str,raw:bool=False, **kwargs: Any) -> Union[requests.Response, Dict[str, Any], bytes]:
        """
        发送 HTTP 请求并统一处理响应和错误
        :param method: HTTP 方法 (get, post, etc.)
        :param url: 请求的 URL
        :param raw: 是否返回原始数据
        :param kwargs: 传递给 `requests.request` 的其他参数
        :return: 响应的 JSON 数据字典。如果发生网络或解析错误，返回包含 errcode 和 errmsg 的字典
        """
        # 为所有请求设置默认超时
        kwargs.setdefault("timeout", 10)
        try:
            response = self._session.request(method, url, **kwargs)
            response.raise_for_status()  # 对 4xx 或 5xx 状态码抛出 HTTPError
        except requests.exceptions.RequestException as e:
            # 统一网络层面的错误格式
            return {"errcode": -1, "errmsg": f"网络请求失败: {e}"}
        if raw:
            ct = response.headers.get("Content-Type", "")
            if ct.startswith("image/"):
                return response.content
            return response

        # 默认 JSON 模式
        try:
            return response.json()
        except json.JSONDecodeError:
            return {"errcode": -1, "errmsg": "无法解析服务器响应内容 (非JSON)"}

